var jfJQMSwipeable = (function($, document, window) {

  var $document = $(document),
      $window = $(window),
      orientation = window.orientation,
      setHeightInterval = null;

  var that = {
    slideDuration: 400, //milliseconds
    bounceEffectDuration: 70, //milliseconds
    bounceEffectWidth: 40, //pixels
    isAnimating: false,
    clickEvent: 'click',
  
    onSwipe: function(e) {
      e.stopPropagation();
      e.preventDefault();
      that.changePosition($(this), e.type.substr(5));
    },
  
    changePosition: function($swipeContainer, direction) {
      if (that.isAnimating) {
        return false;
      }
      
      var config = $swipeContainer.data('jfJQMSwipeable');
      var $wrapper = $swipeContainer.closest('div.jfJQMSwipeableWrapper');
      
      var windowWidth = $wrapper.width();

      var reachedBoundaries = null;
      
      if (direction === 'right') {
        var newPosition = config.currentPosition - 1;
        reachedBoundaries = newPosition < 0;
        var multiplier = 1;
      }
      else {
        var newPosition = config.currentPosition + 1;
        reachedBoundaries = newPosition >= config.numElements;
        var multiplier = -1;
      }
      
      var animate = config.animate;
      
      if (reachedBoundaries) {
        animate = false;
        if ($swipeContainer.attr('data-swipeable-loop') === 'true') {
            newPosition = multiplier === 1 ? config.numElements-1 : 0;
        }
        else {
          newPosition = config.currentPosition;
        }
      }
      else {
        if (config.scrollToTop) { $.mobile.silentScroll(0); }
      }
      
      if (animate) {
        that.isAnimating = true;
        if (reachedBoundaries) { //first or last element => fake bounce effect
          var marginOffset = that.bounceEffectWidth*multiplier;
          $swipeContainer.animate({left: '+='+marginOffset, useTranslate3d: true}, that.bounceEffectDuration, function() {
            $swipeContainer.animate({left: '+='+(marginOffset*-1), useTranslate3d: true}, that.bounceEffectDuration, function() {
              that.isAnimating = false;
            });
          });
        }
        else {
          $swipeContainer.animate({left: '+='+(windowWidth-config.margin)*multiplier, useTranslate3d: true}, that.slideDuration, function() { 
            that.isAnimating = false; 
          });
        }
      }
      else {
        $swipeContainer.css('left', newPosition*(windowWidth-config.margin)*-1);
      }

      config.currentPosition = newPosition;      
      $swipeContainer.data('jfJQMSwipeable', config);
      that.setHeight($swipeContainer);
      that.updateControls($swipeContainer, newPosition, config.numElements);
      $document.trigger('jfJQMSwipeable:changePosition', [config, direction]);
    },
    
    setHeight: function($swipeContainer) {
      var config = $swipeContainer.data('jfJQMSwipeable');
      if (config && config.autoHeight) {
        var $currentElement = $swipeContainer.find('>li:nth-child('+(config.currentPosition+1)+')');
        $swipeContainer.height($currentElement.height());
      }
    },

    onAnimateEnd: function() {
      that.isAnimating = false;
    },
    
    onPageInit: function(e) {
      var $page = $(this);
      that.enhancePage($page);
    },
    
    onPageShow: function() {
      that.adjustSwipeableElements($(this));
    },
    
    onPageBeforeHide: function(e) {
      $(this).find("ul[data-swipeable]").each(function(index, container) {
        var config = $(container).data('jfJQMSwipeable');
        if (config && config.setHeightInterval) {
          clearInterval(config.setHeightInterval);
          config.setHeightInterval = null;
        }
      });
    },
    
    enhancePage: function($page) {
      var $swipeables = $page.find("ul[data-swipeable]");
      if ($swipeables.length) {
        $swipeables.each(that.initSwipeContainer);
        $page.
          bind('pageshow', that.onPageShow).
          bind('pagebeforehide', that.onPageBeforeHide);
      }
      that.adjustSwipeableElements($page);
    },
    
    initSwipeContainer: function(index, container) {
      var $container = $(container);
      that.initWrapper($container);
      $container.hide();

      var $children = $container.children();
      var childrenCount = $children.length;
      var currentPosition = 0;
      for (var i = 0; i < childrenCount; i++) {
        if ($($children[i]).attr('data-swipeable-active')) {
          currentPosition = i;
          break;
        }
      }
      
      try {
        var config = $.parseJSON($container.attr('data-swipeable'));
      }
      catch (e) {}
      
      var config = typeof config === 'object' ? config : {};
      
      config = $.extend(true, {
        numElements: childrenCount,
        currentPosition: currentPosition,
        animate: true,
        margin: 15,
        autoHeight: true
      }, config);

      if (config.autoHeight) {
        config.ghtInterval = setInterval(function() { that.setHeight($container); }, 1000);
      }
      
      $container.data('jfJQMSwipeable', config);
      
      if (childrenCount > 1) {
        that.initControls($container);
      }
      
      $container.bind('swipeleft swiperight', that.onSwipe);

      $container.show();
    },
    
    onOrientationChange: function() {
      if (typeof orientation === 'undefined' || orientation !== window.orientation) {
      	var $page = $.mobile.activePage;
      	orientation = window.orientation;
        var callback = function() { that.adjustSwipeableElements($page); };
      	setTimeout(callback, 300);
      	setTimeout(callback, 1200);
        setTimeout(callback, 2500);
      }
    },
    
    adjustSwipeableElements: function($page) {
      if (!$page || !$page.length) { return; }

      var $wrapper = $page.find('div.jfJQMSwipeableWrapper');
      $.each($wrapper, function(idx, elem) { that.adjustSwipeableElement($(elem)); });
    },
    
    adjustSwipeableElement: function($wrapper) {
      $wrapper.parent().addClass('jfJQMSwipeableInside');

      var windowWidth = $wrapper.width();
      
      var $swipeContainer = $wrapper.find(" > ul"),
          config = $swipeContainer.data('jfJQMSwipeable'),
          $elements = $swipeContainer.children();

      $swipeContainer.css({
        width: $elements.length*windowWidth,
        left: config.currentPosition*(windowWidth-config.margin)*-1
      });

      $elements.css({
        width: windowWidth-(3*config.margin),
        marginRight: config.margin,
        marginLeft: config.margin
      });

      that.updateControls($swipeContainer, config.currentPosition, config.numElements);
      that.setHeight($swipeContainer);
    },

    updateControls: function($swipeContainer, current, count) {
      var $wrapper = $swipeContainer.parent('div.jfJQMSwipeableWrapper');
      var $counter = $wrapper.find('.counter');
      
      if (current === 0) {
        $wrapper.addClass('first');
      }
      else if (current === count-1) {
        $wrapper.addClass('last')
      }
      else {
        $wrapper.removeClass('first last');
      }
      
      var $counterLiteral = $counter.find('.literal');
      $counterLiteral.html((current+1)+' / '+count);
      
      var segmentWidth = 17;
      var maxSegments = 9;
      var $counterVisual = $('span.visual', $counter);
      $counterVisual.css({
        width: segmentWidth*count,
        left: ($wrapper.width()-segmentWidth*count)/2,
        backgroundPosition: (maxSegments*segmentWidth*-1)+(segmentWidth*(current+1))
      });
    },
    
    initWrapper: function($swipeContainer) {
      var $wrapper = $swipeContainer.closest('div.jfJQMSwipeableWrapper');
      if (!$wrapper.length) {
        $wrapper = $('<div class="jfJQMSwipeableWrapper">');
        $swipeContainer.wrap($wrapper);
        $wrapper = $swipeContainer.parent('div.jfJQMSwipeableWrapper'); //need to select $wrapper from DOM to get the "real" DOM representation
        $wrapper.closest('li.ui-li').css({paddingRight: 0});
      }
      
      if (!$swipeContainer.attr('data-swipeable-loop')) {
        $wrapper.addClass('noLoop');
      }
    },
    
    initControls: function($swipeContainer) {
      var config = $swipeContainer.data('jfJQMSwipeable');

      var $wrapper = $swipeContainer.closest('div.jfJQMSwipeableWrapper');
      $wrapper.prepend($('<div class="counter"><span class="visual"></span><span class="literal"></span></div>'));
      
      $left = $('<div class="pager left" data-jfJQMSwipeable-direction="right">');
      $right = $('<div class="pager right" data-jfJQMSwipeable-direction="left">');
      
      $wrapper.prepend($left);
      $wrapper.prepend($right);

      $wrapper.find('[data-jfJQMSwipeable-direction=right]').bind(that.clickEvent, function(e) { e.stopImmediatePropagation(); e.preventDefault(); $swipeContainer.trigger('swiperight'); });
      $wrapper.find('[data-jfJQMSwipeable-direction=left]').bind(that.clickEvent, function(e) { e.stopImmediatePropagation(); e.preventDefault(); $swipeContainer.trigger('swipeleft'); });
      
      that.updateControls($swipeContainer, 0, config.numElements);
    }

  };

  $window.bind('orientationchange', that.onOrientationChange);
  
  if ($.mobile) {
    $("div:jqmData(role='page')").live('pageinit', that.onPageInit);
  }
  else {
    $document.ready(that.onPageInit);
  }  
  
  return that;
})(jQuery, document, window);